#ifndef _WS_CONNECTION_H
#define _WS_CONNECTION_H
#include<string>
#include"asrInst.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include<memory>

//#define PBKDF2_SHA256_STATIC
//#define PBKDF2_SHA256_IMPLEMENTATION
//#include <time.h>//only for linux
//#include <ctype.h>//only for linux
#include <websocketpp/config/asio_client.hpp>
#include <websocketpp/client.hpp>

#include<mutex>
#include<condition_variable>
#include <iostream>
//#include"iflytekAsrRequestParam.h"
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
#include "../utils/log/recoProxyLog.h"
#include "../reporter/msgFactory.h"
using namespace rapidjson;
//typedef shared_ptr<speech::reporter> sReporter;
typedef websocketpp::client<websocketpp::config::asio_client> client;
//typedef websocketpp::client<websocketpp::config::asio_tls_client> client;

using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;
using namespace std;
using namespace speech;
// pull out the type of messages sent by our config
typedef websocketpp::config::asio_client::message_type::ptr message_ptr;
typedef websocketpp::lib::shared_ptr<boost::asio::ssl::context> context_ptr;
typedef client::connection_ptr connection_ptr;
typedef shared_ptr<thread>  sThread;
typedef shared_ptr<msgFactory>                       sMsgFactory;
namespace speech
{
class wsConnection{
public:
	typedef wsConnection type;
	wsConnection(std::string uri,sMsgFactory pMsg)
		:_uri(uri),
		//_pCbFunc(pCb),
		//_pCbObj(pObj),
		_pMsg(pMsg),
		//_sReporter(s_reporter),
		_bClientOpen(false),
		_bClientClose(false)
	{
		_uri = uri;
		//_bNeedClose = false;
	}
	~wsConnection()
	{
		_sConThread->join();
	}

	void init()
	{
		_sConThread = make_shared< thread>(&wsConnection::run,this);	
	}
	void close(){
		std::unique_lock<std::mutex> lock(_conMutex);
		//_bNeedClose = true;
		//always wait for xunfei to close
		try{
			if(!_bClientClose)
				_client.close(_clientConHdl->get_handle(),websocketpp::close::status::going_away,"");
		}
		catch (websocketpp::exception const & e) {
			std::cout << "when close " <<e.what() << std::endl;
		}
		lock.unlock();
	}
	
	int sendMsg(int iMsgType,std::string& msg)
	{
		std::unique_lock<std::mutex> lock(_conMutex);
		while (!_bClientOpen)
			_conCond.wait(lock);
		lock.unlock();
		websocketpp::lib::error_code ec;
		//std::string msg = _requestParam.getFrame(iMsgType,pBuf,iBufLen);
		//std::cout << "---------" << msg << "----" << std::endl;
		char forPrint[10240];
        sprintf(forPrint,"sendMsg %s",msg.c_str());
#ifdef LOGFILE
        log::getInstance()->getLog()->info(forPrint);
#else
		printf("%s\n",forPrint);
#endif
		try
		{
			if(!_bClientClose)
				_client.send(_clientConHdl->get_handle(),msg.c_str(),websocketpp::frame::opcode::text,ec);
			if (ec)
			{
				std::cout << "message send error, msg: " << msg	<< std::endl;
			}
		}
		catch (websocketpp::exception const & e) 
		{
			std::cout << "catch exception when send message error is " << e.what() << std::endl;
		}
		if (ec)
			return -1;

		return 0;
	} 
private:
	int run()
	{
		//while(!_bNeedClose)
		{
			try{
				_client.set_access_channels(websocketpp::log::alevel::all);
				_client.clear_access_channels(websocketpp::log::alevel::frame_payload);
				_client.init_asio();
				_client.set_socket_init_handler(bind(&type::on_socket_init,this,::_1));
				_client.set_message_handler(bind(&type::on_message,this,::_1,::_2));
				_client.set_open_handler(bind(&type::on_open,this,::_1));
				_client.set_close_handler(bind(&type::on_close,this,::_1));
				_client.set_fail_handler(bind(&type::on_fail,this,::_1));

				std::string uri = _uri;//_requestParam.createUrl();
				std::cout << "Uri is "<<uri << std::endl;
				websocketpp::lib::error_code ec;
				client::connection_ptr con = _client.get_connection(uri, ec);
				if (ec) {
					std::cout << "could not create connection because: " << ec.message() << std::endl;
					//return -1;
				}
				_clientConHdl = con ; //con->get_handle();
				_client.connect(con);
				_client.run();
			}
			catch (websocketpp::exception const & e) {
				std::cout << e.what() << std::endl;
			}
			catch (std::exception const & e) {
				std::cout << e.what() << std::endl;
			} catch (...) {
				std::cout << "other exception" << std::endl;
			}
		}

		return 0;
	}
	void on_message( websocketpp::connection_hdl hdl, message_ptr msg) {
		std::cout << "on_message called with hdl: " << hdl.lock().get()
				  << " and message: " << msg->get_payload()
				  << std::endl;
		int ret = _pMsg->processMsg(msg->get_payload().c_str());
		if(2== ret)
			close();
		//_sReporter->processMsg(msg->get_payload().c_str());
		//websocketpp::lib::error_code ec;
		//close();
	}
	void on_socket_init(websocketpp::connection_hdl) {
		std::cout << "on_socket_init"  << std::endl;	
	}
#if 0
	/// Verify that one of the subject alternative names matches the given hostname
	bool verify_subject_alternative_name(const char * hostname, X509 * cert) {
		STACK_OF(GENERAL_NAME) * san_names = NULL;
		
		san_names = (STACK_OF(GENERAL_NAME) *) X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
		if (san_names == NULL) {
			return false;
		}
		
		int san_names_count = sk_GENERAL_NAME_num(san_names);
		
		bool result = false;
		
		for (int i = 0; i < san_names_count; i++) {
			const GENERAL_NAME * current_name = sk_GENERAL_NAME_value(san_names, i);
			
			if (current_name->type != GEN_DNS) {
				continue;
			}
			
			char const * dns_name = (char const *) ASN1_STRING_get0_data(current_name->d.dNSName);
			
			// Make sure there isn't an embedded NUL character in the DNS name
			if (ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) {
				break;
			}
			// Compare expected hostname with the CN
			result = (strcasecmp(hostname, dns_name) == 0);
		}
		sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
		
		return result;
	}

	/// Verify that the certificate common name matches the given hostname
	bool verify_common_name(char const * hostname, X509 * cert) {
		// Find the position of the CN field in the Subject field of the certificate
		int common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name(cert), NID_commonName, -1);
		if (common_name_loc < 0) {
			return false;
		}
		
		// Extract the CN field
		X509_NAME_ENTRY * common_name_entry = X509_NAME_get_entry(X509_get_subject_name(cert), common_name_loc);
		if (common_name_entry == NULL) {
			return false;
		}
		
		// Convert the CN field to a C string
		ASN1_STRING * common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
		if (common_name_asn1 == NULL) {
			return false;
		}
		
		char const * common_name_str = (char const *) ASN1_STRING_get0_data(common_name_asn1);
		
		// Make sure there isn't an embedded NUL character in the CN
		if (ASN1_STRING_length(common_name_asn1) != strlen(common_name_str)) {
			return false;
		}
		
		// Compare expected hostname with the CN
		return (strcasecmp(hostname, common_name_str) == 0);
	}

	/**
	 * This code is derived from examples and documentation found ato00po
	 * http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/example/cpp03/ssl/client.cpp
	 * and
	 * https://github.com/iSECPartners/ssl-conservatory
	 */
	bool verify_certificate(const char * hostname, bool preverified, boost::asio::ssl::verify_context& ctx) {
		// The verify callback can be used to check whether the certificate that is
		// being presented is valid for the peer. For example, RFC 2818 describes
		// the steps involved in doing this for HTTPS. Consult the OpenSSL
		// documentation for more details. Note that the callback is called once
		// for each certificate in the certificate chain, starting from the root
		// certificate authority.

		// Retrieve the depth of the current cert in the chain. 0 indicates the
		// actual server cert, upon which we will perform extra validation
		// (specifically, ensuring that the hostname matches. For other certs we
		// will use the 'preverified' flag from Asio, which incorporates a number of
		// non-implementation specific OpenSSL checking, such as the formatting of
		// certs and the trusted status based on the CA certs we imported earlier.
		int depth = X509_STORE_CTX_get_error_depth(ctx.native_handle());

		// if we are on the final cert and everything else checks out, ensure that
		// the hostname is present on the list of SANs or the common name (CN).
		if (depth == 0 && preverified) {
			X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
			
			if (verify_subject_alternative_name(hostname, cert)) {
				return true;
			} else if (verify_common_name(hostname, cert)) {
				return true;
			} else {
				return false;
			}
		}

		return preverified;
	}

	/// TLS Initialization handler
	/**
	 * WebSocket++ core and the Asio Transport do not handle TLS context creation
	 * and setup. This callback is provided so that the end user can set up their
	 * TLS context using whatever settings make sense for their application.
	 *
	 * As Asio and OpenSSL do not provide great documentation for the very common
	 * case of connect and actually perform basic verification of server certs this
	 * example includes a basic implementation (using Asio and OpenSSL) of the
	 * following reasonable default settings and verification steps:
	 *
	 * - Disable SSLv2 and SSLv3
	 * - Load trusted CA certificates and verify the server cert is trusted.
	 * - Verify that the hostname matches either the common name or one of the
	 *   subject alternative names on the certificate.
	 *
	 * This is not meant to be an exhaustive reference implimentation of a perfect
	 * TLS client, but rather a reasonable starting point for building a secure
	 * TLS encrypted WebSocket client.
	 *
	 * If any TLS, Asio, or OpenSSL experts feel that these settings are poor
	 * defaults or there are critically missing steps please open a GitHub issue
	 * or drop a line on the project mailing list.
	 *
	 * Note the bundled CA cert ca-chain.cert.pem is the CA cert that signed the
	 * cert bundled with echo_server_tls. You can use print_client_tls with this
	 * CA cert to connect to echo_server_tls as long as you use /etc/hosts or
	 * something equivilent to spoof one of the names on that cert 
	 * (websocketpp.org, for example).
	 */
	context_ptr on_tls_init(const char * hostname, websocketpp::connection_hdl) {
		context_ptr ctx = websocketpp::lib::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::sslv23);

		try {
			ctx->set_options(boost::asio::ssl::context::default_workarounds |
							 boost::asio::ssl::context::no_sslv2 |
							 boost::asio::ssl::context::no_sslv3 |
							 boost::asio::ssl::context::single_dh_use);


			ctx->set_verify_mode(boost::asio::ssl::verify_peer);
			ctx->set_verify_callback(bind(&type::verify_certificate, hostname, ::_1, ::_2));

			// Here we load the CA certificates of all CA's that this client trusts.
			ctx->load_verify_file("cacert.pem");
		} catch (std::exception& e) {
			std::cout << e.what() << std::endl;
		}
		return ctx;
	}
#endif
	void on_fail(websocketpp::connection_hdl hdl) {
		std::unique_lock<std::mutex> lock(_conMutex);
		_bClientClose = true;
		lock.unlock();
        client::connection_ptr con = _client.get_con_from_hdl(hdl);
        
        std::cout << "Fail handler" << std::endl;
        std::cout << con->get_state() << std::endl;
        std::cout << con->get_local_close_code() << std::endl;
        std::cout << con->get_local_close_reason() << std::endl;
        std::cout << con->get_remote_close_code() << std::endl;
        std::cout << con->get_remote_close_reason() << std::endl;
        std::cout << con->get_ec() << " - " << con->get_ec().message() << std::endl;
	}
	void on_open(websocketpp::connection_hdl hdl) {
		std::cout << "on_open"  << std::endl;	
		std::unique_lock<std::mutex> lock(_conMutex);
		_conCond.notify_all();
		_bClientOpen = true;
		lock.unlock();
	}
	void on_close(websocketpp::connection_hdl) {
		std::unique_lock<std::mutex> lock(_conMutex);
		_bClientClose = true;
		lock.unlock();
		std::cout << "on_close"  << std::endl;	
	}
	std::string                  _uri;
	//cbReporter                   _pCbFunc;
	//void*                        _pCbObj;
	//reporter*                    _sReporter;
	sMsgFactory                   _pMsg;
	client                       _client;	
	bool                         _bClientOpen;
	bool                         _bClientClose;
	bool                         _bNeedClose;
	client::connection_ptr       _clientConHdl;
	std::condition_variable      _conCond;
	std::mutex                   _conMutex;
	sThread                      _sConThread;
};
}
#endif
